home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / language / harvest.cpt / Harvest C / Examples / SC.018.StdFile / StdFile.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-04-01  |  61.4 KB  |  1,878 lines

  1. /*------------------------------------------------------------------------------
  2. #
  3. #    Apple Macintosh Developer Technical Support
  4. #
  5. #    Standard File Sample Application
  6. #
  7. #    StdFile
  8. #
  9. #    StdFile.c    -    C Source
  10. #
  11. #    Copyright ⌐ 1989 Apple Computer, Inc.
  12. #    All rights reserved.
  13. #
  14. #    Versions:    
  15. #                1.00                04/89
  16. #
  17. #    Components:
  18. #                StdFile.c            April 1, 1989
  19. #                StdFile.p            April 1, 1989
  20. #                StdFile.h            April 1, 1988
  21. #                StdFile.r            April 1, 1988
  22. #                StdFile.rsrc        April 1, 1988
  23. #                CStdFile.make        April 1, 1989
  24. #                PStdFile.make        April 1, 1988
  25. #
  26.  
  27. OBJECTIVES
  28. ----------
  29. This program attempts to demonstrate the following techniques:
  30.  
  31.     - Normal use of SFGetFile and SFPutFile
  32.     - Normal use of SFPGetFile and SFPPutFile. This includes use of Custom
  33.         Dialogs with handling of extra items through the implementation of a
  34.         DlgHook.
  35.             - First time initialization
  36.             - Extra Simple buttons (Quit/Directory/ThisDir)
  37.             - Radio buttons (file format, types of files to show)
  38.             - Aliasing click on some buttons to clicks on other buttons
  39.             - Regenerating the list of files displayed
  40.     - Creating a full pathname from the reply record (using Working Directories
  41.         or DirID)
  42.     - Selecting a directory (ala MPW's "GetFileName -d")
  43.     - Simple file filter (checks file type)
  44.     - Complex file filter (looking inside the file)
  45.     - Adding and deleting and extra List Manager lists. This is shown for both
  46.         SFGetFile and SFPutFile.
  47.     - Select multiple files using one of two methods
  48.         - Replace SF's list with one of your own
  49.         - Add a second list to the Dialog Box. This method is not shown
  50.             explicitly. Rather, I show how to install and dispose of the actual
  51.             list item. Inserting filenames into the list is left as an exercise
  52.             to you, the Programmer.
  53.     - Setting the starting Directory/Volume
  54.     - Describe pending update event clogging
  55.  
  56.  
  57. NOTES
  58. -----
  59.         - This application assumes existance of HFS. It makes HFS calls and
  60.             accesses HFS data structures without first checking to see if HFS
  61.             exists on this machine.
  62.  
  63. ------------------------------------------------------------------------------*/
  64.  
  65. #include <Types.h>
  66. #include <QuickDraw.h>
  67. #include <ToolUtils.h>
  68. #include <Events.h>
  69. #include <Controls.h>
  70. #include <Windows.h>
  71. #include <Dialogs.h>
  72. #include <Menus.h>
  73. #include <Desk.h>
  74. #include <SegLoad.h>
  75. #include <Files.h>
  76. #include <OSEvents.h>
  77. #include <Traps.h>
  78. #include <Fonts.h>
  79. #include <OSUtils.h>
  80. #include <Resources.h>
  81. #include <Memory.h>
  82. #include <Packages.h>
  83. #include <Lists.h>
  84. #include "StdFile.h"
  85.  
  86. typedef struct StringHolder {
  87.     struct StringHolder    **link;
  88.     Str255    title;
  89. } StringHolder, *StringHolderPtr, **StringHolderHdl;
  90.  
  91.  
  92. #define SFSaveDisk        0x214        /* Negative of current volume refnum [WORD]    */
  93. #define CurDirStore        0x398        /* DirID of current directory [LONG]        */
  94. #define ResLoad            0xA5E        /* Boolean ResLoad setting [WORD]            */
  95. #define TopMapHndl        0xA50        /* 1st map in resource list [Handle]        */
  96.  
  97.  
  98. #define firstTime        -1  /* the first time our hook is called, it is passed a -1 */
  99.  
  100. #define reDrawList        101 /* returning 101 as item number will cause the FILE list
  101.                                 to be recalculated */
  102.  
  103. #define BTNON            1   /* Control value for on */
  104. #define BTNOFF            0   /* Control value for off */
  105.  
  106. #define HWCfgFlags        0xB22    /* Low memory global used to check if A/UX is running */
  107.  
  108.  
  109. Boolean            doneFlag;        /* set to TRUE when time to quit */
  110. SysEnvRec        theWorld;
  111. Boolean            gHaveAUX;        /* set to TRUE if running under A/UX */
  112. EventRecord        myEvent;
  113. WindowPtr        whichWindow;
  114. Boolean            WNEIsImplemented;
  115. SFReply            reply;            /* used in all SF samples */
  116. SFTypeList        typeList;        /* typelist for all SF samples */
  117. OSErr            err;            /* used in all OS calls */
  118. Point            gLocation = {0x40,0x40};
  119. Point            gZeroPt = {0,0};
  120.  
  121. /* for normal SFPPutFile sample */
  122. Boolean            textOnly;
  123. short            MySaveDisk;
  124. Rect            freeItemBox;
  125. Str255            myStr, myStr2;
  126.  
  127. /* for SetDirectory sample */
  128. long            MyCurDir;
  129. Boolean            CurDirValid;
  130.  
  131. /* for MultiFile and ListsNPut samples */
  132. ListHandle        LHandle;
  133.  
  134. /* for PutOptions sample */
  135. short            OptionNumber;
  136. DialogPtr        OptionsDPtr;
  137.  
  138. /* for MultiFile */
  139. StringHolderHdl    firstName;
  140. Boolean            namesChanged;
  141. Boolean            needToUpdate;
  142.  
  143.  
  144.  
  145. /* Define HiWrd and LoWrd macros for efficiency. */
  146.  
  147. #define HiWrd(aLong)    (((aLong) >> 16) & 0xFFFF)
  148. #define LoWrd(aLong)    ((aLong) & 0xFFFF)
  149.  
  150. /* Define TopLeft and BotRight macros for convenience. Notice the implicit */
  151. /* dependency on the ordering of fields within a Rect */
  152.  
  153. #define TopLeft(aRect)    (* (Point *) &(aRect).top)
  154. #define BotRight(aRect)    (* (Point *) &(aRect).bottom)
  155.  
  156. /* _DataInit() is part of the MPW runtime library. This external */
  157. /* reference to it is done so that we can unload its segment, %A5Init. */
  158.  
  159.  
  160. void Initialize(void);
  161. void EventLoop(void);
  162. void DoCommand(long mResult);
  163. void SetRadioButton(DialogPtr dialog,short item,short state);
  164. void ShowAboutMeDialog(void);
  165. char *pStrcat(char *s,char *t);
  166. char *pStrcpy(char *s,char *t);
  167. char *PathNameFromDirID(long DirID,short vRefNum,char *s);
  168. char *PathNameFromWD(short vRefNum,char *s);
  169. void ShowSelection(char *s);
  170. void ShowCancelled(void);
  171. void doNormalGet(void);
  172. void doNormalPut(void);
  173. void doNormalPGet(void);
  174. void doNormalPPut(void);
  175. void doFileFilter(void);
  176. void doGetDirectory(void);
  177. void doMultiFile(void);
  178. void doListsNPutFile(void);
  179. void doPutOptions(void);
  180. void doIdleUpdates(void);
  181. void doForceDirectory(void);
  182.  
  183.  
  184. /* Let's do it... */
  185.  
  186. main()
  187. {
  188.  
  189.     MaxApplZone();                    /* expand the heap so code segments load at the top */
  190.  
  191.     Initialize();                    /* initialize the program */
  192.  
  193.     EventLoop();                    /* call the main event loop */
  194. }
  195.  
  196. Boolean TrapAvailable(tNumber,tType)
  197.     short        tNumber;
  198.     TrapType    tType;
  199. {
  200.     return NGetTrapAddress(tNumber, tType) != GetTrapAddress(_Unimplemented);
  201. }
  202.  
  203. void Initialize()
  204. {
  205.     Handle        menuBar;
  206.     short        *flagptr;
  207.  
  208.     InitGraf((Ptr) &qd.thePort);
  209.     InitFonts();
  210.     InitWindows();
  211.     InitMenus();
  212.     TEInit();
  213.     InitDialogs(nil);
  214.     InitCursor();
  215.  
  216.     WNEIsImplemented = TrapAvailable(_WaitNextEvent, ToolTrap);
  217.     
  218.     SysEnvirons(curSysEnvVers,&theWorld);
  219.     flagptr = (short *) HWCfgFlags;
  220.     if (*flagptr & (1 << 9))
  221.           gHaveAUX = 1;            /* Do Have A/UX */
  222.     else
  223.         gHaveAUX = 0;            /* Don't have A/UX */
  224.  
  225.     doneFlag = false;
  226.     MySaveDisk = *(short *)SFSaveDisk + 1;    /*so we're sure that they're different*/
  227.  
  228.     menuBar = GetNewMBar(rMenuBar);        /* read menus into menu bar */
  229.     if (menuBar == nil) ExitToShell();
  230.     SetMenuBar(menuBar);                    /* install menus */
  231.     DisposHandle(menuBar);
  232.     AddResMenu(GetMHandle(mApple), 'DRVR');    /* add DA names to Apple menu */
  233.     DrawMenuBar();
  234.  
  235. } /*Initialize*/
  236.  
  237.  
  238. void EventLoop()
  239. {
  240.     Boolean            haveEvent;
  241.     short            deskPart;        /* result from FindWindow */
  242.  
  243.     do {
  244.         if ( WNEIsImplemented )
  245.             haveEvent = WaitNextEvent(everyEvent, &myEvent, 0x7fffffff, nil);
  246.         else {
  247.             SystemTask();
  248.             haveEvent = GetNextEvent(everyEvent, &myEvent);
  249.         }
  250.  
  251.         deskPart = FindWindow(myEvent.where, &whichWindow);
  252.         if ( deskPart != inSysWindow ) InitCursor();
  253.  
  254.         if (haveEvent) {
  255.             switch (myEvent.what) {
  256.                 case mouseDown:
  257.                     switch (deskPart) {
  258.                         case inSysWindow:
  259.                             SystemClick(&myEvent, whichWindow);
  260.                             break;
  261.                         case inMenuBar:
  262.                             DoCommand(MenuSelect(myEvent.where));
  263.                             break;
  264.                     }
  265.                     break;
  266.  
  267.                 case keyDown:
  268.                 case autoKey:
  269.                     if ((myEvent.modifiers&cmdKey) != 0) {
  270.                         DoCommand(MenuKey(myEvent.message&charCodeMask));
  271.                     }
  272.                     break;
  273.             }
  274.         }
  275.     }
  276.     while (!doneFlag);
  277. }
  278.  
  279.  
  280. /************************ Performed the selected command *************************/
  281.  
  282. void DoCommand(mResult)
  283.     long    mResult;
  284. {
  285.     short    theItem;
  286.     short    theMenu;
  287.     Str255    name;
  288.     GrafPtr    oldPort;
  289.  
  290.     theItem = LoWrd(mResult);
  291.     theMenu = HiWrd(mResult);
  292.  
  293.     switch (theMenu) {
  294.  
  295.         case mApple:
  296.             if (theItem == iAboutMe) {
  297.                 ShowAboutMeDialog();
  298.             } else {
  299.                 GetItem(GetMHandle(mApple),theItem,&name);
  300.                 GetPort(&oldPort);
  301.                 OpenDeskAcc(&name);
  302.                 SetPort(oldPort);
  303.             };
  304.             break;
  305.         case mFile:
  306.             switch (theItem) {
  307.                 case iQuit:
  308.                     doneFlag = true;
  309.                     break;
  310.             };
  311.             break;
  312.         case mEdit:
  313.             SystemEdit(theItem-1);
  314.             break;
  315.         case mSFile:
  316.             switch (theItem) {
  317.                 case iNormalGet:        doNormalGet();        break;
  318.                 case iNormalPut:        doNormalPut();        break;
  319.                 case iNormalPGet:        doNormalPGet();        break;
  320.                 case iNormalPPut:        doNormalPPut();        break;
  321.                 case iFileFilter:        doFileFilter();        break;
  322.                 case iGetDirectory:        doGetDirectory();    break;
  323.                 case iMultiFile:        doMultiFile();        break;
  324.                 case iListsNPutFile:    doListsNPutFile();    break;
  325.                 case iPutOptions:        doPutOptions();        break;
  326.                 case iIdleUpdates:        doIdleUpdates();    break;
  327.                 case iForceDirectory:    doForceDirectory();    break;
  328.             };
  329.             break;
  330.     };
  331.     HiliteMenu(0);
  332. }
  333.  
  334.  
  335.  
  336. /** SetRadioButton ************************************************************/
  337. /*
  338. /*    Handy routine for setting the value of a radio button. Given a dialog
  339. /*    pointer, and item number, and a state, this routine will take care of
  340. /*    the rest.
  341. /*
  342. /******************************************************************************/
  343.  
  344. void SetRadioButton(DialogPtr dialog,short item,short state)
  345. {
  346.     short        kind;
  347.     Handle        h;
  348.     Rect        r;
  349.  
  350.     GetDItem(dialog,item,&kind,&h,&r);
  351.     SetCtlValue((ControlHandle)h,state);
  352. }
  353.  
  354.  
  355. /** ShowAboutMeDialog *********************************************************/
  356. /*
  357. /*    Shows the obligatory vanity box. Normally shows the standard boring
  358. /*    Developer Technical Support About box, but can be enlivened by holding
  359. /*    the Command key when making the menu selection. Rick Blair says that in the
  360. /*    future, we'll have better ones...
  361. /*
  362. /******************************************************************************/
  363.  
  364. void ShowAboutMeDialog()
  365. {
  366.     if ((myEvent.modifiers & cmdKey) == 0) {
  367.         Alert(rAboutMeDLOG, nil);
  368.     } else {
  369.         Alert(rRealAboutMeDLOG, nil);
  370.     }
  371. }
  372.  
  373.  
  374. /** pStrcat / pStrCpy *********************************************************/
  375. /*
  376. /*    A couple of utility routines. C is thoughtless enough to not really 
  377. /*    support P-strings. In order to perform string copies and concatenations,
  378. /*    I have to use these routines.
  379. /*
  380. /******************************************************************************/
  381.  
  382. char *pStrcat( char *s, char *t)
  383. {
  384.      char *s2;
  385.     short tLen;
  386.  
  387.     s2 = s + *s;
  388.     *s += (tLen = *t);
  389.     for (++tLen; --tLen; s2[tLen] = t[tLen]);
  390.     return (s);
  391. }
  392.  
  393. char *pStrcpy( char *s, char *t)
  394. {
  395.     short    tLen;
  396.  
  397.     for (tLen = *t + 1; tLen--; s[tLen] = t[tLen]);
  398.     return (s);
  399. }
  400.  
  401.  
  402. /** PathNameFromDirID *********************************************************/
  403. /*
  404. /*    Given a DirID and real vRefnum, this routine will create and return the
  405. /*    full pathname that corresponds to it. It does this by calling PBGetCatInfo
  406. /*    for the given directory, and finding out its name and the DirID of its
  407. /*    parent. It the performs the same operation on the parent, sticking ITS
  408. /*    name onto the beginning of the first directory. This whole process is
  409. /*    carried out until we have processed the root directory (identified with
  410. /*    a DirID of 2.
  411. /*
  412. /*    NOTE: This routine is now A/UX friendly. A/UX likes sub-directories
  413. /*            separated by slashes in a pathname. This routine automatically
  414. /*            uses colons or slashes as separators based on the value of the
  415. /*            global gHasAUX.  This global must be initialized correctly for
  416. /*            this routine to do its thing. However, because of this dependancy
  417. /*            on the idiosyncracies of file systems, generating full pathnames
  418. /*            for other than display purposes is discouraged; it's changed in
  419. /*            the past when A/UX was implemented, and it may change again in
  420. /*            the future it support for other file systems such as ProDOS,
  421. /*            MS-DOS, or OS/2 are added.
  422. /*
  423. /******************************************************************************/
  424.  
  425. char *PathNameFromDirID(long DirID,short vRefNum,char * s)
  426. {
  427.     CInfoPBRec    block;
  428.     Str255        directoryName;
  429.  
  430.     *s = 0;
  431.     block.dirInfo.ioNamePtr = directoryName;
  432.     block.dirInfo.ioDrParID = DirID;
  433.  
  434.     do {
  435.         block.dirInfo.ioVRefNum = vRefNum;
  436.         block.dirInfo.ioFDirIndex = -1;
  437.         block.dirInfo.ioDrDirID = block.dirInfo.ioDrParID;
  438.  
  439.         err = PBGetCatInfo(&block,false);
  440.         if (gHaveAUX) {
  441.             if (directoryName[1] != '/')
  442.                 /* If this isn't root (i.e. '/'), append a slash ('/') */
  443.                 pStrcat((char *) directoryName,(char *) "\p/");
  444.         } else 
  445.             /* Append a Macintosh style colon (':') */
  446.             pStrcat((char *) directoryName,(char *) "\p:");
  447.         pStrcat((char *) directoryName,s);
  448.         pStrcpy(s,(char *) directoryName);
  449.     } while (block.dirInfo.ioDrDirID != 2);
  450.  
  451.     return(s);
  452. }
  453.  
  454.  
  455. /** PathNameFromWD ************************************************************/
  456. /*
  457. /*    Given an HFS working directory, this routine returns the full pathname
  458. /*    that corresponds to it. It does this by calling PBGetWDInfo to get the
  459. /*    VRefNum and DirID of the real directory. It then calls PathNameFromDirID,
  460. /*    and returns its result.
  461. /*
  462. /******************************************************************************/
  463.  
  464. char *PathNameFromWD(short vRefNum,char *s)
  465. {
  466.  
  467.     WDPBRec    myBlock;
  468.  
  469.     /*
  470.     /* PBGetWDInfo has a bug under A/UX 1.1.  If vRefNum is a real vRefNum
  471.     /* and not a wdRefNum, then it returns garbage.  Since A/UX has only 1
  472.     /* volume (in the Macintosh sense) and only 1 root directory, this can
  473.     /* occur only when a file has been selected in the root directory (/).
  474.     /* So we look for this and hardcode the DirID and vRefNum. */
  475.  
  476.     if (gHaveAUX && (vRefNum == -1))
  477.         return(PathNameFromDirID(2,-1,s));
  478.  
  479.     myBlock.ioNamePtr = nil;
  480.     myBlock.ioVRefNum = vRefNum;
  481.     myBlock.ioWDIndex = 0;
  482.     myBlock.ioWDProcID = 0;
  483.  
  484.     /* Change the Working Directory number in vRefnum into a real vRefnum */
  485.     /* and DirID. The real vRefnum is returned in ioVRefnum, and the real */
  486.     /* DirID is returned in ioWDDirID. */
  487.  
  488.     PBGetWDInfo(&myBlock,false);
  489.  
  490.     return(PathNameFromDirID(myBlock.ioWDDirID,myBlock.ioWDVRefNum,s));
  491. }
  492.  
  493.  
  494. /** ShowSelection *************************************************************/
  495. /*
  496. /*    This routine accepts a string as input, prepends STR#(256,4) - which
  497. /*    should be "The item selected was " - and shows a NoteAlert with the
  498. /*    result.
  499. /*
  500. /******************************************************************************/
  501.  
  502. void ShowSelection(char *s)
  503. {
  504.     Str255 tStr;
  505.  
  506.     GetIndString(&tStr,rStrMisc,4);
  507.     ParamText(pStrcat((char *) tStr,s),nil,nil,nil);
  508.     NoteAlert(rShowSelectionDLOG, nil);
  509. }
  510.  
  511.  
  512. /** ShowCancelled *************************************************************/
  513. /*
  514. /*    Shows STR#(256,5) in a NoteAlert. This string says in English "The Cancel
  515. /*    button was pressed."
  516. /*                                                        - Douglas Hofstadter
  517. /*
  518. /******************************************************************************/
  519.  
  520. void ShowCancelled()
  521. {
  522.     Str255    tStr;
  523.  
  524.     GetIndString(&tStr,rStrMisc,5);
  525.     ParamText(&tStr,nil,nil,nil);
  526.     NoteAlert(rShowSelectionDLOG,nil);
  527. }
  528.  
  529.  
  530. /** doNormalGet ***************************************************************/
  531. /*
  532. /*    Simplest form of SFGetFile. This routine puts up a GetFile dialog with
  533. /*    a request to show all files. When this is done, a check is made to see
  534. /*    which of the Save or Cancel buttons were pressed. If the Save button was
  535. /*    pressed, ShowSelection is called to display which file was selected. If the
  536. /*    Cancel button was pressed, a dialog is shown saying so.
  537. /*
  538. /*    NOTE: The second parameter - the prompt string - is ignored in SFGetFile
  539. /*            calls. As noted on page I-523 of Inside Mac, it is there for
  540. /*            historical reasons only.
  541. /*
  542. /******************************************************************************/
  543.  
  544. void doNormalGet()
  545. {
  546.     Str255    s;
  547.  
  548.     SFGetFile(gLocation,            /* location */
  549.         "\pSpace for Rent",            /* vestigial string */
  550.         nil,                        /* fileFilter */
  551.         -1,                            /* numtypes; -1 means all */
  552.         &typeList,                    /* array to types to show */
  553.         nil,                        /* dlgHook */
  554.         &reply);                    /* record for returned values */
  555.  
  556.     if (reply.good)
  557.         ShowSelection(pStrcat(PathNameFromWD(reply.vRefNum,(char *) s),(char *) reply.fName));
  558.     else
  559.         ShowCancelled();
  560. }
  561.  
  562.  
  563. /** doNormalPut ***************************************************************/
  564. /*
  565. /*    Simplest form of SFPutFile. This routine puts up a PutFile dialog with a
  566. /*    prompt and suggested file name.
  567. /*
  568. /******************************************************************************/
  569.  
  570. void doNormalPut()
  571.  
  572. {
  573.     Str255    s;
  574.  
  575.     SFPutFile(gLocation,            /* location */
  576.         "\pSave document as:",        /* prompt string */
  577.         "\pDoug",                    /* original name */
  578.         nil,                        /* dlgHook */
  579.         &reply);                    /* record for returned values */
  580.  
  581.     if (reply.good)
  582.         ShowSelection(pStrcat(PathNameFromWD(reply.vRefNum,(char *) s),(char *) reply.fName));
  583.     else
  584.         ShowCancelled();
  585. }
  586.  
  587.  
  588. /** doNormalPGet **************************************************************/
  589. /*
  590. /*    SFPGetFile with Dialog Hook and Simple File Filter. Depending on the value
  591. /*    of the Global variable "textOnly", it shows either TEXT files, or TEXT and
  592. /*    APPL files. The value of "textOnly" is determined by the states of two
  593. /*    radio buttons that are added to the dialog box. Our dlgHook routine is used
  594. /*    to initialize the radio buttons and handle hits on them. When there is a
  595. /*    hit on a radio button, "textOnly" is set to either TRUE or FALSE, and a
  596. /*    special command is sent back to Standard File, telling it to regenerate
  597. /*    the list of file names it is displaying. This sample consists of 3 parts:
  598. /*
  599. /*    doNormalPGet - Called by doCommand. This routine initializes a variable
  600. /*        for our file filter, and then calls SFPGetFile with pointers to
  601. /*        two other routines and a resource number for a special dialog box
  602. /*        with extra items in it.
  603. /*
  604. /*    SimpleFileFilter - Specified in our SFPGetFile call to be called to
  605. /*        specify whether or not a file should be displayed. All TEXT files
  606. /*        are displayed. If the global variable 'textOnly' is FALSE, then
  607. /*        applications (APPL files) are also displayed. NO other files are
  608. /*        displayed.
  609. /*
  610. /*    MySFGetHook - A routine that is called to handle hits on the non-standard
  611. /*        items in our dialog box. These items are:
  612. /*            - 2 radio buttons that affect the setting of the 'textOnly'
  613. /*                variable,
  614. /*            - a Quit button that causes the GetFile dialog box to be put
  615. /*                away (like pressing Cancel), but also causes this demo to be
  616. /*                exitted.
  617. /*
  618. /*        The dlgHook is also used to perform some special initialization
  619. /*        on the items in the dialog box. Standard file does this by calling
  620. /*        this routine with a bogus 'item' number of -1. When we get this
  621. /*        number, we initialize our radio buttons, and change the text in the
  622. /*        Open button.
  623. /*
  624. /*        The radio buttons are used to determine what files will appear in
  625. /*        the files list. It does this by apporpriately setting the 'textOnly'
  626. /*        variable for SimpleFileFilter, and then telling Standard File that
  627. /*        the file list needs to be regenerated by passing back 101 as the
  628. /*        the function result.
  629. /*
  630. /*        The Quit button causes the dialog box to go away by returning the
  631. /*        'getCancel' to Standard File. It also sets the 'doneFlag' variable
  632. /*        to TRUE for our MainEventLoop to notice.
  633. /*
  634. /******************************************************************************/
  635.  
  636. pascal short MySFGetHook(short MySFItem,DialogPtr theDialog)
  637. {
  638. #define textButton 11
  639. #define textAppButton 12
  640. #define quitButton 13
  641.  
  642.     Handle        itemToChange;    /* needed for GetDItem and SetCtlValue */
  643.     Rect        itemBox;        /* needed for GetDItem */
  644.     short        itemType;        /* needed for GetDItem */
  645.     Str255        buttonTitle;    /* needed for GetIndString */
  646.  
  647.     /* MySFGetHook is a function that requires that an item number be passed    */
  648.     /* back from it. Normally, this is the same item number that was passed        */
  649.     /* to us, but not necessarily. For instance, clicks on the Quit button        */
  650.     /* get translated into clicks on the Cancel button. We could also return    */
  651.     /* values that cause the file names to be redrawn or have the whole event    */
  652.     /* ignored. However, by default, we'll return what was sent to us.            */
  653.  
  654.     switch (MySFItem) {
  655.         case firstTime:
  656.         /* Before the dialog is drawn, our hook gets called with a -1. */
  657.         /* This gives us the opportunity to change things like Button titles, etc. */
  658.  
  659.         /* Set the textAppButton to OFF, the textButton to ON */
  660.  
  661.             SetRadioButton(theDialog,textAppButton,BTNOFF);
  662.             SetRadioButton(theDialog,textButton,BTNON);
  663.  
  664.         /* Get the Button title from the resource fork. If we can't get the */
  665.         /* resource, we just won't change the open Button's title*/
  666.  
  667.             GetIndString(&buttonTitle,rStrMisc,1);
  668.             if (*buttonTitle != 0) { /* if we really got the resource */
  669.                 GetDItem(theDialog,getOpen,&itemType,&itemToChange,&itemBox);
  670.                 SetCTitle((ControlHandle)itemToChange,&buttonTitle);
  671.             };
  672.             break;
  673.  
  674.         case textButton:
  675.  
  676.         /* Turn the textAppButton OFF, the textButton ON and redraw the list*/
  677.  
  678.             if (!textOnly) {
  679.                 SetRadioButton(theDialog,textAppButton,BTNOFF);
  680.                 SetRadioButton(theDialog,textButton,BTNON);
  681.                 textOnly = true;
  682.                 return(reDrawList); /* we must tell SF to redraw the list */
  683.             };
  684.             break;
  685.  
  686.         case textAppButton:
  687.  
  688.         /* Turn the textButton OFF, the textAppButton ON and redraw the list*/
  689.  
  690.             if (textOnly) {
  691.                 SetRadioButton(theDialog,textButton,BTNOFF);
  692.                 SetRadioButton(theDialog,textAppButton,BTNON);
  693.                 textOnly = false;
  694.                 return(reDrawList); /* we must tell SF to redraw the list */
  695.             };
  696.             break;
  697.  
  698.         case quitButton:
  699.  
  700.         /* Alias clicks on the Quit button to clicks on the Cancel Button. Also, */
  701.         /* set 'doneFlag' to TRUE so that the MainEventLoop terminates. */
  702.  
  703.             doneFlag = true;
  704.             return (getCancel); /* Pass SF back a 'cancel Button' */
  705.  
  706.         default:
  707.             return(MySFItem);
  708.     };
  709. }
  710.  
  711. pascal Boolean SimpleFileFilter(ParmBlkPtr p)
  712. {
  713.  
  714.     /* If we have a 'TEXT' file, or we have an 'APPL' file and 'textOnly' is */
  715.     /* FALSE, then signify that we should show the file by returning FALSE */
  716.  
  717.     if ((p->fileParam.ioFlFndrInfo.fdType == 'TEXT') ||
  718.         ((!textOnly) && (p->fileParam.ioFlFndrInfo.fdType == 'APPL')))
  719.             return(false);
  720.     else
  721.         return(true);
  722. }
  723.  
  724.  
  725. void doNormalPGet()
  726. {
  727.     textOnly = true;
  728.     SFPGetFile(gLocation,    /* location */
  729.         "\pSpace for Rent",    /* vestigial string */
  730.         SimpleFileFilter,    /* fileFilter */
  731.         -1,                    /* numtypes; -1 means all */
  732.         &typeList,            /* array to types to show */
  733.         MySFGetHook,        /* dlgHook */
  734.         &reply,                /* record for returned values */
  735.         rSFPGetFileDLOG,    /* ID of Custom Dialog */
  736.         nil);                /* ModalDialog filterProc */
  737. }
  738.  
  739. /** doNormalPPut **************************************************************/
  740. /*
  741. /*    Normal SFPPutFile sample. This custom PutFile routine adds several new
  742. /*    to the dialog box. There are 2 radio buttons that determine what format
  743. /*    the user wants to save the information in. There is a Quit buttons that
  744. /*    will cause us to leave this program. And there is a Static Text item that
  745. /*    displays the amount of free space left on the disk. This text item is
  746. /*    updated whenever we detect that the volume we are currently looking at is
  747. /*    not the same as the last volume we looked at. Therefore, we maintain a
  748. /*    variable called MySaveDisk to track this. This sample consists of 3 parts:
  749. /*
  750. /*    doNormalPPut - This routine, called by doCommand, calls SFPPutFile. Note
  751. /*        the extra 'P' in the name. This toolbox call allows us to specify a
  752. /*        customized dialog box, and dlgHook that handles hits on special
  753. /*        items.
  754. /*
  755. /*    MySFPutHook - A routine that is called to handle the non-standard items
  756. /*        in our dialog box. These items are:
  757. /*            - 2 radio buttons that affect the setting of the 'textOnly'
  758. /*                variable,
  759. /*            - a Quit button that causes the GetFile dialog box to be put
  760. /*                away (like pressing Cancel), but also causes this demo to be
  761. /*                exitted,
  762. /*            - a UserItem that displays the amount of free space on the
  763. /*                current volume.
  764. /*
  765. /*        The dlgHook is also used to perform some special initialization
  766. /*        on the items in the dialog box. Standard file does this by calling
  767. /*        this routine with a bogus 'item' number of -1. When we get this
  768. /*        number, we initialize our radio buttons, change the text in the
  769. /*        save button, and prepare the user item by initializing the routine
  770. /*        that will draw it and getting the text that will appear in it.
  771. /*
  772. /*        The radio buttons are used to determine what format the file will be
  773. /*        saved in. They are functionless in this sample, merely changing
  774. /*        their state and setting a global variable for possible use later.
  775. /*
  776. /*        The Quit button causes the dialog box to go away by returning the
  777. /*        'putCancel' to Standard File. It also sets the 'doneFlag' variable
  778. /*        to TRUE for our MainEventLoop to notice.
  779. /*
  780. /*        Finally, this routine performs one other function. When SFPPutFile
  781. /*        is called, we make note of the current volume (the one that will
  782. /*        be displayed in the dialog box). This routine is responsible for
  783. /*        checking to see whenever we change volumes, and to update the
  784. /*        Free Space notice accordingly. It does this by looking at the value
  785. /*        stored at SFSaveDisk. Whenever that value changes, we have changed
  786. /*        volumes and need to make a PBHGetVInfo call to find out how much
  787. /*        room is left on the disk.
  788. /*
  789. /*    DrawFreeSpaceItem - used to draw a user item that displays the amount of
  790. /*        free space left on the disk.
  791. /*
  792. /******************************************************************************/
  793.  
  794. pascal void DrawFreeSpaceItem(WindowPtr theWindow,short itemNo)
  795. {
  796. #pragma unused (theWindow,itemNo)
  797.     TextBox(myStr + 1, *myStr, &freeItemBox, teJustCenter);
  798. }
  799.  
  800.  
  801. pascal short MySFPutHook(short MySFItem,DialogPtr theDialog)
  802. {
  803. #define jquitButton 9
  804. #define jtextButton 10
  805. #define formatButton 11
  806. #define freeSpaceItem 12  /* DITL item number of free space static text */
  807.  
  808.     Handle            itemToChange;    /* needed for GetDItem and SetCtlValue */
  809.     short            itemType;        /* needed for GetDItem */
  810.     Rect            itemBox;        /* needed for GetDItem */
  811.     HParamBlockRec    myHPB;
  812.  
  813.  
  814.  
  815. /* MySFPutHook is a function that requires that an item number be passed    */
  816. /* back from it. Normally, this is the same item number that was passed        */
  817. /* to us, but not necessarily. For instance, clicks on the Quit button        */
  818. /* get translated into clicks on the Cancel button. We could also return    */
  819. /* values that cause the file names to be redrawn or have the whole event    */
  820. /* ignored. However, by default, we'll return what was sent to us.            */
  821.  
  822. /* Before the dialog is drawn, our hook gets called with a -1. */
  823. /* This gives us the opportunity to change things like Button titles, etc. */
  824.  
  825.     if (MySFItem == firstTime) {
  826.  
  827.     /* Set the FormatButton to ON, the textButton to OFF*/
  828.  
  829.         SetRadioButton(theDialog,formatButton,BTNON);
  830.         SetRadioButton(theDialog,jtextButton,BTNOFF);
  831.         textOnly = false;
  832.  
  833.     /* Get the text for the Save button from the resource fork. */
  834.     /* If we can't get the resource, we just won't change the Button's title*/
  835.  
  836.         GetIndString(&myStr,rStrMisc,1);
  837.         if (*myStr != 0) {            /*if we really got the resource*/
  838.             GetDItem(theDialog,putSave,&itemType,&itemToChange,&itemBox);
  839.             SetCTitle((ControlHandle)itemToChange,&myStr);
  840.         };
  841.  
  842.     /* Set up our routine to draw our user item */
  843.  
  844.         GetDItem(theDialog,freeSpaceItem,&itemType,&itemToChange,&freeItemBox);
  845.         SetDItem(theDialog,freeSpaceItem,itemType,(Handle)DrawFreeSpaceItem,
  846.                  &freeItemBox);
  847.  
  848.     /* Get the text to be used  in our user item. */
  849.  
  850.         GetIndString(&myStr2,rStrMisc,2);
  851.  
  852.     }; /* if MySFItem == firstTime */
  853.  
  854. /* Check to see if we have changed drives. If so, then we need to update */
  855. /* the text in our UserItem. */
  856.  
  857.     if (MySaveDisk != *(short *)SFSaveDisk) {
  858.         /* set up the block for the PBHGetVInfo call*/
  859.         myHPB.volumeParam.ioNamePtr = nil;            /* we don't care about the name */
  860.         myHPB.volumeParam.ioVRefNum = -*(short *)SFSaveDisk;
  861.         myHPB.volumeParam.ioVolIndex = 0;            /* use ioVRefNum only */
  862.  
  863.         err = PBHGetVInfo(&myHPB,false);
  864.  
  865.         if (err == noErr) {
  866.             NumToString(((long)myHPB.volumeParam.ioVFrBlk * myHPB.volumeParam.ioVAlBlkSiz) / 1024,&myStr);
  867.         } else  /* Handle error - we can't get the free space size! */
  868.             pStrcpy((char *) myStr,(char *)  "\p??");
  869.  
  870.     /* Add the file size to 'K free' (or whatever) */
  871.  
  872.         pStrcat((char *) myStr,(char *) myStr2);
  873.  
  874.     /* Draw the new freeBlocks in our userItem */
  875.  
  876.         DrawFreeSpaceItem(theDialog,freeSpaceItem);
  877.  
  878.     /* SFSaveDisk changed, so we need to update mySaveDisk*/
  879.  
  880.         MySaveDisk = *(short *)SFSaveDisk;
  881.  
  882.     }; /*IF mySaveDisk != SFSaveDisk^*/
  883.  
  884.     switch(MySFItem) {
  885.  
  886.         case jtextButton:
  887.  
  888.         /* Turn the FormatButton OFF, the textButton ON */
  889.  
  890.             if (!textOnly) {
  891.                 SetRadioButton(theDialog,formatButton,BTNOFF);
  892.                 SetRadioButton(theDialog,jtextButton,BTNON);
  893.                 textOnly = true; /* change our flag accordingly */
  894.             };    /*if not textOnly*/
  895.             break;
  896.  
  897.         case formatButton:
  898.  
  899.         /* Turn the textButton OFF, the FormatButton ON */
  900.  
  901.             if (textOnly) {
  902.                 SetRadioButton(theDialog,jtextButton,BTNOFF);
  903.                 SetRadioButton(theDialog,formatButton,BTNON);
  904.                 textOnly = false; /* change our flag accordingly */
  905.             };    /*if not textOnly*/
  906.             break;
  907.  
  908.         case jquitButton:
  909.             doneFlag = true;
  910.             return(putCancel); /* Pass SF back a 'cancel Button' */
  911.  
  912.     };    /* switch */
  913.     return(MySFItem);
  914. }
  915.  
  916.  
  917. void doNormalPPut()
  918. {
  919.     SFPPutFile(gLocation,        /* location */
  920.         "\pSave document as:",    /* prompt string */
  921.         "\pDoug",                /* original name */
  922.         MySFPutHook,            /* dlgHook */
  923.         &reply,                    /* record for returned values */
  924.         rSFPPutFileDLOG,        /* ID of custom Dialog */
  925.         nil);                    /* ModalDialog filterProc */
  926. }
  927.  
  928.  
  929. /** ComplexFileFilter *********************************************************/
  930. /*
  931. /*    Sample of a less trivial file filter. This routine is responsible for
  932. /*    displaying only files that have 'ALRT' resources.  It shows how to
  933. /*    correctly look inside a file for qualifying information, and demonstrates
  934. /*    how to get around a particular quirk when attempting multiple access to
  935. /*    the resource fork.
  936. /*
  937. /*    In order to save time, the first thing we do is turn off resource loading;
  938. /*    we just want to count the number of ALRT resources, and don't care about
  939. /*    the actual data itself. So I save off the current ResLoad setting, and
  940. /*    then set it to FALSE.
  941. /*
  942. /*    I then prepare to open the resource fork of the file. However, I can't
  943. /*    simply call OpenResFile. OpenResFile only works for files that are in
  944. /*    the default directory. Standard file makes HFS calls when it can, and
  945. /*    explicitly uses DirIDs when getting file names and other information.
  946. /*    Therefore, I can't count on the default directory being set to the dir-
  947. /*    ectory that we are examining. In order to call OpenResFile, I have to set
  948. /*    the default directory.
  949. /*
  950. /*    So I take the VRefNum and DirID passed to me, and I create a Working
  951. /*    Directory from them. I save the old default directory, and then set it
  952. /*    to the WD I just created.
  953. /*
  954. /*    So far, so good. I am now ready to open the resource fork of the file,
  955. /*    call Count1Resources (which limits enumeration to the current file only,
  956. /*    excluding other files in the resource chain), and close the file. But
  957. /*    wait! I must make sure that I am not closing a file that I don't really
  958. /*    want to close. For example, one of the files I may be examining may be
  959. /*    myself. If I were to blindly close that file, I would be closing my CODE
  960. /*    resources, removing them from memory. Empirical experience has told me that
  961. /*    this is not good.
  962. /*
  963. /*    So I have to make sure that I don't close my own resource fork. I could
  964. /*    possibly check the refnum that I get back from OpenResFile against one
  965. /*    that I could get by calling CurResFile at the start of the program. If
  966. /*    they match, then I am looking at my own file, and I shouldn't close it. But
  967. /*    this doesn't work always. For instance, I also want to make sure that I
  968. /*    don't close the System Resource file when I point and click my way into
  969. /*    the System Folder. What if there are DA's open? I don't want to close them.
  970. /*    How about the Fonts/DA's file used by Suitcase? What about the DeskTop
  971. /*    file? Basically, I need a way to determine on the fly if a resource file
  972. /*    is already open before I try to open it myself.
  973. /*
  974. /*    Well, there is a way. Within the parameter block passed to me is a field
  975. /*    called ioFlAttrib. It is described on page IV-122 of Inside Mac. As
  976. /*    described there, bit 2 contains the state of the resource fork. If the fork
  977. /*    is open, then the bit is set. All that is necessary for me to do is check
  978. /*    the state of this bit before I open the file. If the bit is clear, then
  979. /*    I need to close the file when I am done.
  980. /*
  981. /*    But wait! There's more. This technique - alas! - will not work under
  982. /*    MultiFinder! Why? Because this bit is set even if another program in
  983. /*    another partition has the resource fork open. I would test that bit, find
  984. /*    that the file is open, open the file for myself, count the number of
  985. /*    resources, and then leave the file open. This now leaves another resource
  986. /*    file in the resource chain - one that wasn't there before and that
  987. /*    shouldn't be there now. Another technique is needed.
  988. /*
  989. /*    Fortunately, one is at hand. On page I-115 of Inside Mac is a brief
  990. /*    description of a low-memory variable called TopMapHndl. This contains
  991. /*    a handle to the resource map of the first resource file in the chain.
  992. /*    Since a resource file is made the current resource file when OpenResFile
  993. /*    is called if it is actually being opened, we can test the value of
  994. /*    TopMapHndl to see if the file was really opened. If the value of TopMapHndl
  995. /*    changes after OpenResFile, then the resource fork was open for this
  996. /*    application, and should be closed when we are done with it. If TopMapHndl
  997. /*    doesn't change, then no new resource maps were added to the chain; no
  998. /*    new files were opened. Therefore, the file doesn't need to be closed later.
  999. /*    So far, this solution seems to be the best.
  1000. /*
  1001. /*    After doing all of this, we are almost done. All that needs to be done is
  1002. /*    restore the default directory and the value of ResLoad to their previous
  1003. /*    settings. When we have done so, we can return to Standard File.
  1004. /*
  1005. /******************************************************************************/
  1006.  
  1007. pascal Boolean ComplexFileFilter(ParmBlkPtr p)
  1008. {
  1009.     short            refnum, tRefNum;
  1010.     WDPBRec            WDRec;
  1011.     ParamBlockRec    oldVol, newVol;
  1012.     Boolean            oldResLoad, closeIt;
  1013.     Handle            tHandle;
  1014.     Boolean            returnVal;
  1015.  
  1016.     returnVal = true;        /* Don't show it -- default */
  1017.  
  1018. /* Save the current setting of ResLoad. Then set it to FALSE. */
  1019.  
  1020.     oldResLoad = *(Boolean *)ResLoad;
  1021.     SetResLoad(false);
  1022.  
  1023. /* Save the current default directory/volume. */
  1024.  
  1025.     oldVol.volumeParam.ioNamePtr = nil;
  1026.     PBGetVol(&oldVol,false);
  1027.  
  1028. /* Create a working directory for the directory we are examining. */
  1029.  
  1030.     WDRec.ioNamePtr = nil;
  1031.     WDRec.ioVRefNum = p->fileParam.ioVRefNum;
  1032.     WDRec.ioWDProcID = 'ERIK';
  1033.     WDRec.ioWDDirID = *(long *)CurDirStore;
  1034.  
  1035.     PBOpenWD(&WDRec,false);
  1036.  
  1037. /* Set the Working Directory we just created to be the default. */
  1038.  
  1039.     newVol.volumeParam.ioNamePtr = nil;
  1040.     newVol.volumeParam.ioVRefNum = WDRec.ioVRefNum;
  1041.  
  1042.     PBSetVol(&newVol,false);
  1043.  
  1044. /* Check the current value of TopMapHndl, open the resource file, and */
  1045. /* check it again. If it changed, then note that we should close this */
  1046. /* file later when we are done with it. */
  1047.  
  1048.     tHandle = *(Handle *)TopMapHndl;
  1049.     refnum = OpenResFile(p->fileParam.ioNamePtr);
  1050.     if (tHandle != *(Handle *)TopMapHndl)
  1051.         closeIt = true;
  1052.     else
  1053.         closeIt = false;
  1054.  
  1055. /* If we successfully opened the file, then count the number of ALRT    */
  1056. /* resources in it. Call UseResFile to make sure that the file we just    */
  1057. /* 'opened' is the current one. We have to do this, as OpenResFile will    */
  1058. /* not make the file the current one if it is already open. So save the    */
  1059. /* current resFile, (possibly) change it to the one we want, read the    */
  1060. /* number of ALRT resources in it, and then set the current resource    */
  1061. /* file back to what it was. Finally, if we need to close the file we    */
  1062. /* opened, do so. */
  1063.  
  1064.     if (ResError() == noErr) {
  1065.         tRefNum = CurResFile();
  1066.         UseResFile(refnum);
  1067.         if (Count1Resources('ALRT') > 0) returnVal = false;
  1068.         UseResFile(tRefNum);
  1069.         if (closeIt) CloseResFile(refnum);
  1070.     };
  1071.  
  1072. /* All done! Reset the default directory and ResLoad, and then blow. */
  1073.  
  1074.     PBSetVol(&oldVol,false);
  1075.     PBCloseWD(&WDRec,false);
  1076.     SetResLoad(oldResLoad);
  1077.  
  1078.     return(returnVal);
  1079. }
  1080.  
  1081. void doFileFilter()
  1082. {
  1083.     SFGetFile(gLocation,    /* location */
  1084.         "\pSpace for Rent",    /* vestigial string */
  1085.         ComplexFileFilter,    /* fileFilter */
  1086.         -1,                    /* numtypes; -1 means all */
  1087.         &typeList,            /* array to types to show */
  1088.         nil,                /* dlgHook */
  1089.         &reply);            /* record for returned values */
  1090. }
  1091.  
  1092.  
  1093. /** doGetDirectory ************************************************************/
  1094. /*
  1095. /*    Shows how to modify SFGetFile to allow you to select a directory. This
  1096. /*    mimics the "GetFileName -d" function of MPW. As a matter of fact, the
  1097. /*    DLOG and DITL used in this sample are taken directly out of MPW.
  1098. /*
  1099. /*    There are 2 major additions in the dialog used in this sample:
  1100. /*        - a Simple button that lets one select the directory that is
  1101. /*            currently highlighted in the list of directories,
  1102. /*        - a Simple button at the top of the dialog that lets the user select
  1103. /*            the directory that we are currently *IN*.
  1104. /*
  1105. /*    No new techniques are really used in this sample. Hits on the two simple
  1106. /*    buttons are handled by a dlgHook called MyGetDirHook. Depending on
  1107. /*    which button is hit, we set the global variable MyCurDir to either
  1108. /*    reply.fType (for the currently highlighted directory) or CurDirStore^ (for
  1109. /*    the directory that we are currently in). We then simulate a hit on the
  1110. /*    Cancel button so that Standard File will return to our application.
  1111. /*
  1112. /*    However, when we get back to our application, there is no way for it to
  1113. /*    determine if we simulated a click on the Cancel Button, or if the user
  1114. /*    actually clicked on it; reply.good will be FALSE in either case. So we also
  1115. /*    set the value of global variable CurDirValid to TRUE if we only simulated
  1116. /*    a click on Cancel.
  1117. /*
  1118. /*    We also use the "Item== -1" feature to set up a prompt string and init-
  1119. /*    alize the value of CurDirValid to FALSE.
  1120. /*
  1121. /******************************************************************************/
  1122. pascal short MyGetDirHook(short item,DialogPtr dPtr)
  1123. {
  1124. /* Equates for the itmes that I've added */
  1125. #define getDirButton 11
  1126. #define getDirNowButton 12
  1127. #define getDirMessage 13
  1128.  
  1129.     Str255    messageTitle;
  1130.     short    kind;
  1131.     Handle    h;
  1132.     Rect    r;
  1133.  
  1134.     switch (item) {
  1135.         case firstTime:
  1136.  
  1137.             /* Read in the prompt string from the resource fork, and initialize */
  1138.             /* CurDirValid to FALSE. */
  1139.  
  1140.             GetIndString(&messageTitle,rStrMisc,3);
  1141.             GetDItem(dPtr,getDirMessage,&kind,&h,&r);
  1142.             SetIText(h,&messageTitle);
  1143.             CurDirValid = false;
  1144.             break;
  1145.         case getDirButton:
  1146.             if (reply.fType != 0) {
  1147.                 MyCurDir = reply.fType;
  1148.                 CurDirValid = true;
  1149.                 return(getCancel);
  1150.             };
  1151.             break;
  1152.         case getDirNowButton:
  1153.             MyCurDir = *(long *)CurDirStore;
  1154.             CurDirValid = true;
  1155.             return(getCancel);
  1156.     };
  1157.     return(item);    /* By default, return the item passed to us. */
  1158. }
  1159.  
  1160.  
  1161. pascal Boolean FoldersOnly(ParmBlkPtr p)
  1162. {
  1163.     /* Normally, folders are ALWAYS shown, and aren't even passed to        */
  1164.     /* this file filter for judgement. Under such circumstances, it is        */
  1165.     /* only necessary to blindly return TRUE (allow no files whatsoever).    */
  1166.     /* However, Standard File is not documented in such a manner, and        */
  1167.     /* this feature may not be TRUE in the future. Therefore, we DO check    */
  1168.     /* to see if the entry passed to us describes a file or a directory.    */
  1169.  
  1170.     if ((p->fileParam.ioFlAttrib & 0x10) != 0)
  1171.         return(false);
  1172.     return(true);
  1173. }
  1174.  
  1175.  
  1176. void doGetDirectory()
  1177. {
  1178.     Str255    s;
  1179.  
  1180.     SFPGetFile(gLocation,    /* location */
  1181.         "\pSpace for Rent",    /* vestigial string */
  1182.         FoldersOnly,        /* fileFilter */
  1183.         -1,                    /* numtypes; -1 means all */
  1184.         &typeList,            /* array to types to show */
  1185.         MyGetDirHook,        /* dlgHook */
  1186.         &reply,                /* record for returned values */
  1187.         rGetDirectoryDLOG,
  1188.         nil);
  1189.  
  1190.     if (CurDirValid)
  1191.         ShowSelection(PathNameFromDirID(MyCurDir,-*(short *)SFSaveDisk,(char *)s));
  1192. }
  1193.  
  1194.  
  1195. /** MultiFile *****************************************************************/
  1196. /*
  1197. /*    WARNING! The following is an experiment that failed! I was playing around
  1198. /*            with different ways for implementing a facility for multiple
  1199. /*            file handling. This was one of them. As with all experiments that
  1200. /*            fail, it's pretty ugly. The following should NOT be used as a
  1201. /*            sample on how to do things right, but as a lesson on how NOT to
  1202. /*            do things.
  1203. /*
  1204. /*    This sample is an attempt to implement the facilty to let the user choose
  1205. /*    more than one file at once. It does this by remembering all of the files
  1206. /*    passed to the fileFilter, and using those names to create a new, application
  1207. /*    governed filename list that will replace the one displayed by Standard File.
  1208. /*    However, this experiment seems doomed to failure, for the following reasons:
  1209. /*
  1210. /*        - Icons are not displayed next to the names of files. The resulting
  1211. /*            presentation is more disconcerting that I thought.
  1212. /*        - The current directory button (the one displayed above the list of
  1213. /*            filenames) disappears. This seems due to the fact that that pop-up
  1214. /*            menu-like item is not actually a dialog item, and is somehow attached
  1215. /*            to the list of filenames handled by Standard File. Since I move that
  1216. /*            list off of the window so that I can display my own, the current
  1217. /*            directory button seems to go with it.
  1218. /*        - Updating the list is difficult. The normal sequence of events would
  1219. /*            desirably be: 1) have the fileFilter create a linked list of names
  1220. /*            to appear in the dialog, 2) insert those names into the List Manager
  1221. /*            list, and 3) display the list. However, by following this outine,
  1222. /*            the list does not get updated, as the update region for that area
  1223. /*            is empty. So, at some time, we need to invalidate the area
  1224. /*            that holds our list. But there is no convenient place in which to
  1225. /*            do that. We can't call InvalRect within our fileFilter routine, as
  1226. /*            we don't have the DialogPtr at the time (needed to get the bounding
  1227. /*            rectangle of the list item). Neither can we call InvalRect within
  1228. /*            step 2, as we are between BeginUpdate/EndUpdates, and calling
  1229. /*            InvalRect would be useless.
  1230. /*        - We can't display directories. They are not offered to the judgement
  1231. /*            of our fileFilter, and hence cannot be added to our list.
  1232. /*        - Key presses would have do be handled manually by our dlgHook.
  1233. /*
  1234. /*    Our thesis is implemented as described, with all of the above limitations.
  1235. /*    What follows is a description of the routines used:
  1236. /*
  1237. /*    ScoopNames - The fileFilter routine that is responsible for remembering
  1238. /*        the names of all the files passed to it by Standard file. The names
  1239. /*        are stored in a singly linked list of handles. Each handle contains
  1240. /*        a Str255 to hold the name, and room for the handle for the next name.
  1241. /*        The root of the list is stored in the global variable 'firstName'.
  1242. /*        The list is used by SetNames to set the data for the list cells.
  1243. /*
  1244. /*    MultiFileHook - Creates the list and installs it as a user item. Also,
  1245. /*        we check on NULL events to see if our list needs to be updated. If
  1246. /*        so, we call InvalRect to force it to be redrawn. Finally, this
  1247. /*        routine disposes of the list if Open or Cancel are pressed.
  1248. /*
  1249. /*    MFModalFilter - Calls LClick for MouseDowns on the list.
  1250. /*
  1251. /*    MFDrawList - Redraws the list. If there are new names to be displayed, then
  1252. /*        they are inserted into the cells. Then LUpdate is called to redraw
  1253. /*        the list.
  1254. /*
  1255. /*    SetNames - Called by MFDrawList when FirstName is != NIL, indicating that
  1256. /*        there are new names to be displayed. We first get a count of the
  1257. /*        number of names in the linked list, and the List Manager list is
  1258. /*        adjusted accordingly. Then, the linked list is traversed, with each
  1259. /*        name being inserted into its appropriate cell. The nodes of the
  1260. /*        linked list are disposed of along the way. When everything is done,
  1261. /*        the List Manager list is appropriately set up, and the linked list is
  1262. /*        disposed of, with FirstName== NIL.
  1263. /*
  1264. /******************************************************************************/
  1265.  
  1266.  
  1267. void SetNames()
  1268. {
  1269.     StringHolderHdl    nextName;
  1270.     short            count;
  1271.     short            delta;
  1272.     Cell            theCell;
  1273.  
  1274.     nextName = firstName;
  1275.     count = 0;
  1276.     if (nextName != nil) {
  1277.         do {
  1278.             count++;
  1279.             nextName = (*nextName)->link;
  1280.         } while (nextName != nil);
  1281.     };
  1282.     
  1283.     delta = count - (*LHandle)->dataBounds.bottom;
  1284.     if (delta < 0)            /* need to remove this many cells */
  1285.         LDelRow(-delta,0,LHandle);
  1286.     else if (delta > 0)        /* need to add this many cells */
  1287.         LAddRow(delta,(*LHandle)->dataBounds.bottom,LHandle);
  1288.         
  1289.     theCell.h = 0;
  1290.     theCell.v = count-1;
  1291.     do {
  1292.         HLock((Handle)firstName);
  1293.         LSetCell((*firstName)->title+1, *((*firstName)->title), theCell, LHandle);
  1294.         HUnlock((Handle)firstName);
  1295.         nextName = (*firstName)->link;
  1296.         DisposHandle((Handle)firstName);
  1297.         firstName = nextName;
  1298.         theCell.v--;
  1299.     } while (firstName != nil);
  1300.     namesChanged = false;
  1301. }
  1302.  
  1303.  
  1304. pascal void MFDrawList(WindowPtr theWindow,short item)
  1305. {
  1306. #define ListItem 11
  1307.  
  1308.     short    kind;
  1309.     Handle    h;
  1310.     Rect    r;
  1311.  
  1312.     if (item == ListItem) {
  1313.         GetDItem(theWindow,ListItem,&kind,&h,&r);
  1314.         EraseRect(&r);
  1315.         FrameRect(&r);
  1316.         LUpdate(theWindow->visRgn,LHandle);
  1317.         if (namesChanged)
  1318.             SetNames();
  1319.     };
  1320. }
  1321.  
  1322. pascal Boolean MFModalFilter(DialogPtr theDialog,EventRecord    * theEvent,short *itemHit)
  1323. {
  1324.  
  1325. #pragma unused (theDialog, itemHit)
  1326.  
  1327.     Boolean    doubleClick;
  1328.     Point    localPt;
  1329.     
  1330.     if (theEvent->what == mouseDown) {
  1331.         localPt = theEvent->where;
  1332.         GlobalToLocal(&localPt);
  1333.         doubleClick = LClick(localPt,theEvent->modifiers,LHandle);
  1334.         /* should check for double clicks here */
  1335.     };
  1336.     return(false);
  1337. }
  1338.  
  1339.  
  1340. pascal short MultiFileHook(short item,DialogPtr dPtr)
  1341. {
  1342. #define ListItem 11
  1343.  
  1344.     short    kind;
  1345.     Handle    h;
  1346.     Rect    r;
  1347.     Rect    rView, dataBounds;
  1348.  
  1349.     switch (item) {
  1350.         case firstTime:
  1351.             GetDItem(dPtr,ListItem,&kind,&h,&r);
  1352.             SetDItem(dPtr,ListItem,kind,(Handle)MFDrawList,&r);
  1353.             rView = r;
  1354.             rView.right -= 15;
  1355.             InsetRect(&rView,1,1);
  1356.             dataBounds.top = 0;
  1357.             dataBounds.left = 0;
  1358.             dataBounds.bottom = 0;
  1359.             dataBounds.right = 1;
  1360.             LHandle = LNew(&rView,    /* position in window */
  1361.                 &dataBounds,        /* initial size of array */
  1362.                 gZeroPt,            /* cell size (0 = default) */
  1363.                 0L,                    /* resource ID of LDEF */
  1364.                 dPtr,                /* window pointer */
  1365.                 true,                /* drawit */
  1366.                 false,                /* has grow */
  1367.                 false,                /* scrollHoriz */
  1368.                 true);                /* scrollVert */
  1369.             break;
  1370.             
  1371.         case 100:
  1372.             if (needToUpdate) {
  1373.                 GetDItem(dPtr,ListItem,&kind,&h,&r);
  1374.                 InvalRect(&r);
  1375.                 needToUpdate = false;
  1376.             };
  1377.             break;
  1378.             
  1379.         case getOpen:
  1380.         case getCancel:
  1381.             LDispose(LHandle);    /* Our linked list of names was already disposed */
  1382.                                 /* of in SetNames, so nothing else to do here... */
  1383.             break;
  1384.     };
  1385.     return(item);
  1386. }
  1387.  
  1388. pascal Boolean ScoopNames(ParmBlkPtr pb)
  1389. {
  1390.     StringHolderHdl    nextName;
  1391.     
  1392.     nextName = (StringHolderHdl)NewHandle(sizeof(StringHolder));
  1393.     HLock((Handle)nextName);
  1394.     pStrcpy((char *) (*nextName)->title,(char *)  pb->fileParam.ioNamePtr);
  1395.     (*nextName)->link = firstName;
  1396.     firstName = nextName;
  1397.     HUnlock((Handle)nextName);
  1398.     namesChanged = true;
  1399.     needToUpdate = true;
  1400.  
  1401.     return(false); /*show everything*/
  1402. }
  1403.  
  1404. void doMultiFile()
  1405. {
  1406.     firstName = nil;
  1407.     namesChanged = false;
  1408.     SFPGetFile(gLocation,    /* location */
  1409.         "\pSpace for Rent",    /* vestigial string */
  1410.         ScoopNames,            /* fileFilter */
  1411.         -1,                    /* numtypes; -1 means all */
  1412.         &typeList,            /* array to types to show */
  1413.         MultiFileHook,        /* dlgHook */
  1414.         &reply,                /* record for returned values */
  1415.         rMultiFileDLOG,        /* ID of Custom Dialog */
  1416.         MFModalFilter);        /* ModalDialog filterProc */
  1417. }
  1418.  
  1419.  
  1420. /** doListsNPutFile ***********************************************************/
  1421. /*
  1422. /*    This sample demonstrates putting a List Manager list into an SFPPutFile
  1423. /*    dialog, describes a problem that can occur by doing so, and shows one
  1424. /*    solution to the problem.
  1425. /*
  1426. /*    Putting another list into a Standard File dialog can be useful for a number
  1427. /*    of reasons. One of them was shown above, where we attempted to replace
  1428. /*    Standard File's list with one of our own. However, the failure we met with
  1429. /*    there forces us to look for another solution. This solution could possibly
  1430. /*    take place in the form of TWO lists: one supplied by Standard file, and the
  1431. /*    other handled by us to hold the multiple file names.
  1432. /*
  1433. /*    While the actual logistics of putting the selected filenames into the list
  1434. /*    as they are selected is left to you, the Programmer, I do show how to
  1435. /*    create and handle the list.
  1436. /*
  1437. /*    While *I* can't think of a reason for wanting to put a second list into
  1438. /*    a PutFile dialog, it is possible that you may wish to. However, there is
  1439. /*    a subtle problem that arises if you do: that of determining when to
  1440. /*    dispose of the list. In the MultiFile example above, we were perfectly
  1441. /*    justified in disposing of the list when the user clicked on Open or
  1442. /*    Cancel. However, in the following example, if were to dispose of the list
  1443. /*    when the user clicked on Save, we may hit a snag. It is possible for the
  1444. /*    user to specify the name of a file that alreay exists. When that happens,
  1445. /*    s/he is presented with another dialog that asks if they are sure of what
  1446. /*    they are doing. At that point, the user could press Cancel, and return us
  1447. /*    to the PutFile dialog, SANS our 2nd list! Neither can we defer disposing
  1448. /*    of list until Standard File is done and returns to our application, as the
  1449. /*    Dialog box we were using is gone, and the List Manager try to perform an
  1450. /*    InvalRect on a non-existant window when it erases its list.
  1451. /*
  1452. /*    There are two solutions to this problem:
  1453. /*
  1454. /*        1) Implement the list as a Custom Control. Then, when it somes time to
  1455. /*            close the dialog box, the custom control will get called with a
  1456. /*            dispCntl message. It can take that opportunity to call LDispose.
  1457. /*
  1458. /*            This technique is not presented here, as it is more appropriate
  1459. /*            for a Custom Control Sample program.  However, it is the suggested
  1460. /*            way to proceed.
  1461. /*
  1462. /*        2) Ensure that the confirmation dialog box never comes up under Standard
  1463. /*            File's control. By calling it ourself, we know what the user chooses.
  1464. /*            If the user presses OK, we delete the offending file, dispose of our
  1465. /*            list, and return to Standard File. If the user presses Cancel, we
  1466. /*            change the click on the Save button into a NULL event. This is the
  1467. /*            algorithm we use below.
  1468. /*
  1469. /*            There is a major disadvantage with this approach, however. With it,
  1470. /*            we cannot implement a safe saving procedure. Normally, the best way
  1471. /*            to save a file (disk space permitting), is to save the data to a
  1472. /*            temporary file, delete the original, and then rename the temporary
  1473. /*            file to that of the original. However, with approach #2, the file
  1474. /*            is deleted well before it is safe to do so.
  1475. /*
  1476. /*            This routine *could* be reworked to avoid this problem. For instance,
  1477. /*            instead of being deleted, the offending file could be renamed to
  1478. /*            something else or moved to another directory.
  1479. /*
  1480. /******************************************************************************/
  1481.  
  1482. pascal void LNPFDrawList(WindowPtr theWindow,short item)
  1483. {
  1484. #define jListItem 9
  1485.  
  1486.     short    kind;
  1487.     Handle    h;
  1488.     Rect    r;
  1489.  
  1490.     if (item == jListItem) {
  1491.         GetDItem(theWindow,jListItem,&kind,&h,&r);
  1492.         FrameRect(&r);
  1493.         LUpdate(theWindow->visRgn,LHandle);
  1494.     };
  1495. }
  1496.  
  1497. pascal Boolean LNPFModalFilter(DialogPtr theDialog,EventRecord    * theEvent,short        * itemHit)
  1498. {
  1499. #pragma unused(theDialog, itemHit)
  1500.     Point    localPt;
  1501.  
  1502.     if (theEvent->what == mouseDown) {
  1503.         localPt = theEvent->where;
  1504.         GlobalToLocal(&localPt);
  1505.         LClick(localPt,theEvent->modifiers,LHandle);
  1506.     };
  1507.     return(false);
  1508. }
  1509.  
  1510.  
  1511. pascal short ListsNPutFileHook(short item,DialogPtr dPtr)
  1512. {
  1513. #define qListItem 9
  1514. #define ExistingFileALRT -3996
  1515.  
  1516.     short        kind;
  1517.     Handle        h;
  1518.     Rect        r;
  1519.     Rect        rView, dataBounds;
  1520.     Str255        Entry;
  1521.     Cell        theCell;
  1522.     short        choice;
  1523.     FInfo        fndrInfo;
  1524.     WDPBRec        WDRec;
  1525.     AlertTHndl    AlertHandle;
  1526.     Point        dLoc;
  1527.     Rect        *tPtr;
  1528.  
  1529.     switch (item) {
  1530.         case firstTime:
  1531.             GetDItem(dPtr,qListItem,&kind,&h,&r);
  1532.             SetDItem(dPtr,qListItem,kind,(Handle)LNPFDrawList,&r);
  1533.             rView = r;
  1534.             rView.right = rView.right - 15;
  1535.             InsetRect(&rView,1,1);
  1536.             dataBounds.top = 0;
  1537.             dataBounds.left = 0;
  1538.             dataBounds.bottom = 0;
  1539.             dataBounds.right = 1;
  1540.             LHandle = LNew(&rView,    /* position in window */
  1541.                 &dataBounds,        /* initial size of array */
  1542.                 gZeroPt,            /* cell size (0== default) */
  1543.                 0L,                    /* resource ID of LDEF */
  1544.                 dPtr,                /* window pointer */
  1545.                 true,                /* drawit */
  1546.                 false,                /* has grow */
  1547.                 false,                /* scrollHoriz */
  1548.                 true);                /* scrollVert */
  1549.             theCell = gZeroPt;
  1550.             do {
  1551.                 GetIndString(&Entry,rStrList,theCell.v + 1);
  1552.                 if (*Entry != 0) {
  1553.                     LAddRow(1,(*LHandle)->dataBounds.bottom,LHandle);
  1554.                     LSetCell(Entry+1,*Entry,theCell,LHandle);
  1555.                 };
  1556.                 theCell.v += 1;
  1557.             } while (*Entry != 0);
  1558.             break;
  1559.  
  1560.         case putSave:
  1561.             WDRec.ioNamePtr = nil;
  1562.             WDRec.ioVRefNum = - *(short *)SFSaveDisk;
  1563.             WDRec.ioWDProcID = 'ERIK';
  1564.             WDRec.ioWDDirID = *(long *)CurDirStore;
  1565.             PBOpenWD(&WDRec,false);
  1566.  
  1567.             err = GetFInfo(&reply.fName,WDRec.ioVRefNum,&fndrInfo);
  1568.             if (err == noErr) {
  1569.                 ParamText(&reply.fName,nil,nil,nil);
  1570.  
  1571.                 /* Before bringing up the Alert that asks for confirmation, we        */
  1572.                 /* have to relocate it. To start with, the window is located at        */
  1573.                 /* 0,0 in the Window Manager port. To move the Alert Window, read    */
  1574.                 /* the resource into memory and change the boundsRect field so        */
  1575.                 /* that its topleft is at 12,100 within Standard File's dialog.        */
  1576.     
  1577.                 AlertHandle = (AlertTHndl)GetResource('ALRT',ExistingFileALRT);
  1578.                 dLoc = TopLeft(dPtr->portRect);        /* get global location of SF's */
  1579.                 LocalToGlobal(&dLoc);                /* dialog box. */
  1580.                 tPtr = &((*AlertHandle)->boundsRect);
  1581.                 tPtr->right        -= tPtr->left;    /* get width and height of the Alert */
  1582.                 tPtr->bottom    -= tPtr->top;    /* into botRight. */
  1583.                 tPtr->left        = dLoc.h + 12;    /* Change Alert.TopLeft to SF.Dlog.TopLeft */
  1584.                 tPtr->top        = dLoc.v + 100;    /* plus 12,100. */
  1585.                 tPtr->right        += tPtr->left;    /* Adjust Alert.BotRight accordingly. */
  1586.                 tPtr->bottom    += tPtr->top;
  1587.                 choice = Alert(ExistingFileALRT,nil);
  1588.                 if (choice == cancel) {  /* the OK button is in the Cancel slot (item #2) */
  1589.                     FSDelete(&reply.fName,WDRec.ioVRefNum);
  1590.                     LDispose(LHandle);
  1591.                 } else {
  1592.                     return(100);    /* Change "Save" into null event */
  1593.                 };
  1594.             }
  1595.             break;
  1596.         case putCancel:
  1597.             LDispose(LHandle);
  1598.             break;
  1599.     }; /* switch */
  1600.     return(item);
  1601. }
  1602.  
  1603.  
  1604. void doListsNPutFile()
  1605. {
  1606.     short    vRefNum;
  1607.  
  1608.     GetVol(nil,&vRefNum);
  1609.     Create("\pDoug",vRefNum,'KAAR','APPL');
  1610.  
  1611.     SFPPutFile(gLocation,        /* location */
  1612.         "\pSave document as:",    /* prompt string */
  1613.         "\pDoug",                /* original name */
  1614.         ListsNPutFileHook,        /* dlgHook */
  1615.         &reply,                    /* record for returned values */
  1616.         rListsNPutFileDLOG,        /* ID of custom Dialog */
  1617.         LNPFModalFilter);        /* ModalDialog filterProc */
  1618. }
  1619.  
  1620.  
  1621. /** doPutOptions **************************************************************/
  1622. /*
  1623. /*    Apple's suggested options box - A modest proposal.
  1624. /*
  1625. /*    With the proliferation of applications and different file types, many
  1626. /*    developers are allowing the user to select from various file formats they
  1627. /*    want to save their data as. In an effort to maintain consistancy among
  1628. /*    all applications in this respect, we are presenting a sample interface for
  1629. /*    letting the user select the file type they want.
  1630. /*
  1631. /*    This is done by adding 2 items to the PutFile dialog box. One is a text
  1632. /*    item that displays the file format that the file will be saved as. The
  1633. /*    second is an "Options╔" button that brings up another dialog box. This
  1634. /*    dialog box conatins a series of radio buttons next to the names of the
  1635. /*    various file formats supported by the application. Also in the second
  1636. /*    dialog box are OK and Cancel buttons. After a selection has been made
  1637. /*    by the user, the text in the PutFile dialog is updated accordingly.
  1638. /*
  1639. /******************************************************************************/
  1640.  
  1641. void SetFormatString(short number,DialogPtr theDialog)
  1642. {
  1643. #pragma unused(number)
  1644. #define FormatString 10
  1645.     short    kind;
  1646.     Handle    h;
  1647.     Rect    r;
  1648.     Str255    title;
  1649.  
  1650.     GetDItem(OptionsDPtr,OptionNumber,&kind,&h,&r);
  1651.     GetCTitle((ControlHandle)h,&title);
  1652.     GetDItem(theDialog,FormatString,&kind,&h,&r);
  1653.     SetIText(h,&title);
  1654. }
  1655.  
  1656.  
  1657. pascal DrawFrame(WindowPtr theWindow,short itemNo)
  1658. {
  1659.     short        kind;
  1660.     Handle        h;
  1661.     Rect        r;
  1662.     PenState    ps;
  1663.  
  1664.     GetDItem(theWindow,itemNo,&kind,&h,&r);
  1665.     GetPenState(&ps);
  1666.     PenSize(2,2);
  1667.     FrameRect(&r);
  1668.     SetPenState(&ps);
  1669. }
  1670.  
  1671. void doOptionsDialog(short    *item)
  1672. {
  1673.     short    newItem;
  1674.     short    itemHit;
  1675.  
  1676.     newItem = OptionNumber;
  1677.     SelectWindow(OptionsDPtr);
  1678.     ShowWindow(OptionsDPtr);
  1679.     do {
  1680.         ModalDialog(nil,&itemHit);
  1681.         if ((itemHit != newItem) && (itemHit > 2)) {
  1682.             SetRadioButton(OptionsDPtr,newItem,BTNOFF);
  1683.             SetRadioButton(OptionsDPtr,itemHit,BTNON);
  1684.             newItem = itemHit;
  1685.         }
  1686.     } while ((itemHit != ok) && (itemHit != cancel));
  1687.     HideWindow(OptionsDPtr);
  1688.     if (itemHit == ok)
  1689.         *item = newItem;
  1690. }
  1691.  
  1692. pascal short PutOptionsHook(short item,DialogPtr theDialog)
  1693. {
  1694. #define FirstRadioButton 3
  1695. #define OptionsButton 9
  1696. #define FrameItem 10
  1697.  
  1698.     short    kind;
  1699.     Handle    h;
  1700.     Rect    r;
  1701.  
  1702.     switch (item) {
  1703.         case firstTime:
  1704.             OptionNumber = FirstRadioButton;
  1705.             SetRadioButton(OptionsDPtr,OptionNumber,BTNON);
  1706.             SetFormatString(OptionNumber,theDialog);
  1707.             GetDItem(OptionsDPtr,FrameItem,&kind,&h,&r);
  1708.             SetDItem(OptionsDPtr,FrameItem,kind,(Handle)DrawFrame,&r);
  1709.             break;
  1710.         case OptionsButton:
  1711.             doOptionsDialog(&OptionNumber);
  1712.             SetFormatString(OptionNumber,theDialog);
  1713.             break;
  1714.     };
  1715.     return(item);
  1716. }
  1717.  
  1718.  
  1719. void doPutOptions()
  1720. {
  1721.     OptionsDPtr = GetNewDialog(rOptionsSubDLOG,nil,(WindowPtr) -1L);
  1722.     SFPPutFile(gLocation,        /* location */
  1723.         "\pSave document as:",    /* prompt string */
  1724.         "\pDoug",                /* original name */
  1725.         PutOptionsHook,            /* dlgHook */
  1726.         &reply,                    /* record for returned values */
  1727.         rOptionsDLOG,            /* ID of custom Dialog */
  1728.         nil);                    /* ModalDialog filterProc */
  1729.     DisposDialog(OptionsDPtr);
  1730. }
  1731.  
  1732. /** doIdleUpdates *************************************************************/
  1733. /*
  1734. /*    There is a problem that Standard File has with updates pending in
  1735. /*    background windows in the current application partition.
  1736. /*
  1737. /*    Standard File calls ModalDialog at the heart of its Main Event Loop.
  1738. /*    ModalDialog calls GetNextEvent, and then calls a filterProc internal
  1739. /*    to Standard File. This filterProc performs some processing on the event,
  1740. /*    (like handling hits on the filename list, the Current Directory button, and
  1741. /*    the Disk Name Icon), and then calls the filterProc specified in SFPxxxFile
  1742. /*    calls.
  1743. /*
  1744. /*    Another of the things that the internal filterProc does is look for NULL
  1745. /*    events. When one is found, the filterProc returns 100 as the "itemHit".
  1746. /*    This forces ModalDialog to return to SF, which in turn can now call
  1747. /*    the dlgHook with the bogus 100 item number, indicating that idle time
  1748. /*    processing can be performed.
  1749. /*
  1750. /*    The problem occurs when there are updates pending in windows open in
  1751. /*    the current application's partition. These updates will 'clog' the event
  1752. /*    queue. When ModalDialog calls GetNextEvent, it will get the update
  1753. /*    event. However, there is no way for ModalDialog to respond to it. It
  1754. /*    therefore passes the event off to the filterProc, which is SF's internal
  1755. /*    one. This filterProc doesn't know how to handle it either. Normally,
  1756. /*    the event would be ignored at this point, and the update event would be
  1757. /*    unresolved. Control returns back to ModalDialog, which calls GetNextEvent
  1758. /*    again, and gets the same udpate event back. NULL events will not get
  1759. /*    returned by GetNextEvent, and, hence, the dlgHook will never get called
  1760. /*    with itemHi==100.
  1761. /*
  1762. /*    This situation can be solved by providing your own filterProc
  1763. /*    procedure to be called after SF's internal filterProc. It will be this
  1764. /*    routine's responsibility to check for update events, and handle them
  1765. /*    appropriately. Usually, this would mean that the update procedure
  1766. /*    within the application would be called, and the update could be cleared.
  1767. /*    However, the filterProc could also just handle update events in the same
  1768. /*    way that Standard File's filterProc handles NULL events. This is done
  1769. /*    by returning 100 in the ItemHit parameter and TRUE as the function result.
  1770. /*    This is the approach taken by the sample below.
  1771. /*
  1772. /******************************************************************************/
  1773.  
  1774.  
  1775. /* This is our dlgHook routine. All it does is wait around for NULL events    */
  1776. /* and draws the current time when it gets one. Note that this routine will    */
  1777. /* NOT get called with ite==100 if we did not have the filter procedure        */
  1778. /* below. */
  1779.  
  1780. pascal short IdleTimeHook(short item,DialogPtr dlg)
  1781. {
  1782. #pragma unused(dlg)
  1783.     unsigned long    dateTime;
  1784.     Str255    result;
  1785.  
  1786.     if (item == 100) {
  1787.         TextMode(srcCopy);
  1788.         MoveTo(250,15);
  1789.         GetDateTime(&dateTime);
  1790.         IUTimeString(dateTime, /* wantSecond == */ true, result);
  1791.         pStrcat((char *) result,(char *) "\p  "); /*pad with spaces to erase longer strings*/
  1792.         DrawString(&result);
  1793.     };
  1794.     return(item);
  1795. }
  1796.  
  1797. pascal Boolean filter(DialogPtr dlg,EventRecord    * evt,short        * itemHit)
  1798. {
  1799.     /* If we get an update event for a window other than the dialog box, change */
  1800.     /* it to a NULL event, and tell ModalDialog that we handled it. */
  1801.  
  1802.     if ((evt->what == updateEvt) && (((void *) evt->message) != ((void *) dlg))) {
  1803.         *itemHit = 100;
  1804.         return(true);
  1805.     };
  1806.     return(false);
  1807. }
  1808.  
  1809. void doIdleUpdates()
  1810. {
  1811.     Rect        r;
  1812.     WindowPtr    tPtr;
  1813.  
  1814.     /* Create a window with a non-empty update region. */
  1815.  
  1816.     r.top = 40; r.left = 10; r.bottom = 340; r.right = 500;
  1817.  
  1818.     tPtr = NewWindow(nil,        /* Window storage */
  1819.         &r,                        /* bounding rectangle */
  1820.         "\pUn-updated Window",
  1821.         true,                    /* is visible */
  1822.         documentProc,            /* procID */
  1823.         (WindowPtr)-1L,            /* bring it up on top */
  1824.         true,                    /* has goaway */
  1825.         0L);                    /* refcon */
  1826.  
  1827.     SFPGetFile(gLocation,        /* location */
  1828.         "\pSpace for Rent",        /* vestigial string */
  1829.         nil,                    /* fileFilter */
  1830.         -1,                        /* numtypes; -1 means all */
  1831.         &typeList,                /* array to types to show */
  1832.         IdleTimeHook,            /* dlgHook */
  1833.         &reply,                    /* record for returned values */
  1834.         -4000,                    /* ID of Normal Dialog */
  1835.         filter);                /* ModalDialog filterProc */
  1836.  
  1837.     DisposeWindow(tPtr);
  1838. }
  1839.  
  1840.  
  1841. /** doForceDirectory **********************************************************/
  1842. /*
  1843. /*    This is a quick sample that shows how to set the initial directory that
  1844. /*    Standard File comes up with. Basically, this is done by storing apporpriate
  1845. /*    values into SFSaveDisk and CurDirStore. In this example, I force the
  1846. /*    directory to be the Blessed Folder as specified by SysEnvirons.
  1847. /*
  1848. /******************************************************************************/
  1849.  
  1850.  
  1851. void doForceDirectory()
  1852. {
  1853.     WDPBRec    pb;
  1854.     Str255    s;
  1855.  
  1856.     pb.ioNamePtr = nil;
  1857.     pb.ioVRefNum = theWorld.sysVRefNum;
  1858.     pb.ioWDIndex = 0;
  1859.     pb.ioWDProcID = 0;
  1860.     PBGetWDInfo(&pb,false);
  1861.  
  1862.     *(long *)CurDirStore = pb.ioWDDirID;
  1863.     *(short *)SFSaveDisk = -pb.ioWDVRefNum;
  1864.  
  1865.     SFGetFile(gLocation,        /* location */
  1866.         "\pSpace for Rent",        /* vestigial string */
  1867.         nil,                    /* fileFilter */
  1868.         -1,                        /* numtypes; -1 means all */
  1869.         &typeList,                /* array to types to show */
  1870.         nil,                    /* dlgHook */
  1871.         &reply);                /* record for returned values */
  1872.  
  1873.     if (reply.good )
  1874.         ShowSelection(pStrcat(PathNameFromWD(reply.vRefNum,(char *) s),(char *) reply.fName));
  1875.     else
  1876.         ShowCancelled();
  1877. }
  1878.